﻿using Elements;
using Elements.Links;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FlowChartCanvas
{
    /// <summary>
    /// 流程图画布
    /// </summary>
    public class FCCanvasV7:Control
    {
        public FCCanvasV7()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint |
                ControlStyles.UserPaint |
                ControlStyles.OptimizedDoubleBuffer, true);
        }

        #region 公共事件

        /// <summary>
        /// 添加连线时，连线的颜色
        /// </summary>
        public event Func<Color> FCC_LinkColor;

        #endregion

        #region 公共类型

        //V5新增

        /// <summary>
        /// 画布模式
        /// </summary>
        public enum FCC_MODE
        {
            //注：文章中说明，方便演示说明，直接定义为中文

            正常模式,
            连线模式,
        }

        #endregion

        #region 公共属性

        /// <summary>
        /// 连线样式
        /// </summary>
        public string FCC_LinkType { get; set; } = "LineLink";

        /// <summary>
        /// 画布模式
        /// </summary>
        public FCC_MODE FCC_Mode { get; set; } = FCC_MODE.正常模式;

        #endregion

        #region 公共方法

        //注：文章中说明，为了方便查看和演示有哪些方法和属性，所以固定开头，可依喜好不要此开头

        /// <summary>
        /// 向当前画布中添加形状
        /// </summary>
        /// <param name="sps"></param>
        public void FCC_AddShapes(List<ShapeBaseV5> sps)
        {
            if (sps == null || sps.Count == 0) return;
            foreach (var item in sps)
            {
                //根据ID去重
                if (!_shapes.Any(a => a.Id == item.Id))
                {
                    _shapes.Add(item);
                }
            }
            //令当前控件失效以重绘
            Invalidate();
        }
        
        /// <summary>
        /// 清空画布中的形状和连线
        /// </summary>
        public void FCC_Clear()
        {
            _shapes.Clear();
            _links.Clear();
            Invalidate();
        }

        /// <summary>
        /// 刷新当前画布
        /// </summary>
        public void FCC_Refresh()
        {
            Invalidate();
        }

        /// <summary>
        /// 获取当前画布所有形状
        /// </summary>
        /// <returns></returns>
        public List<ShapeBaseV5> FCC_GetAllShapes()
        {
            return _shapes;
        }

        #endregion

        #region 私有属性

        //注：文章中说明，不要直接暴露属性，通过方法来控制

        //V5修改

        /// <summary>
        /// 形状集合
        /// </summary>
        List<ShapeBaseV5> _shapes = new List<ShapeBaseV5>();

        /// <summary>
        /// 连线集合
        /// </summary>
        List<LinkBaseV5> _links = new List<LinkBaseV5>();

        Bitmap _bmp;


        /// <summary>
        /// 当前是否有鼠标按下，且有矩形被选中
        /// </summary>
        bool _isMouseDown = false;
        /// <summary>
        /// 最后一次鼠标的位置
        /// </summary>
        Point _lastMouseLocation = Point.Empty;
        /// <summary>
        /// 当前被鼠标选中的矩形
        /// </summary>
        ShapeBaseV5 _selectedShape = null;

        /// <summary>
        /// 添加连线时选中的第一个形状
        /// </summary>
        ShapeBaseV5 _selectedStartShape = null;
        /// <summary>
        /// 添加连线时选中的第一个形状
        /// </summary>
        ShapeBaseV5 _selectedEndShape = null;

        /// <summary>
        /// 是否正添加连线
        /// </summary>
        bool _isAddLink = false;

        //V5新增

        /// <summary>
        /// 连线时，开始形状的连接点
        /// </summary>
        ShapeBaseV5.ShapeLinkPoints _linkPointStart { get; set; } 
        /// <summary>
        /// 连线时，结束形状的连接点
        /// </summary>
        ShapeBaseV5.ShapeLinkPoints _linkPointEnd { get; set; }

        //注：课程中可根据设置的连接样式设置不同的虚线样式

        /// <summary>
        /// 绘制连接中的虚线箭头画笔
        /// </summary>
        Pen _linkingLinePen = new Pen(Color.FromArgb(128, Color.Red), 2) 
        {
            Alignment = PenAlignment.Center,
            CustomEndCap = new AdjustableArrowCap(6, 6, true)
            {
                MiddleInset = 2f,
            },
            DashStyle = DashStyle.Dash,
        };

        #endregion

        #region 私有方法

        #endregion

        #region 重写方法

        protected override void OnPaint(PaintEventArgs e)
        {
            //所有的形状、连线都会在此处绘制到控件上
            //创建内存绘图，将形状和连线绘制到此内存绘图上，然后再一次性绘制到控件上

            //注：后续再优化BMP，只有尺寸变化时才生成

            _bmp = new Bitmap(Width, Height);
            var g = Graphics.FromImage(_bmp);

            //设置显示质量
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

            g.Clear(BackColor);

            //绘制所有形状
            foreach (var sp in _shapes)
            {
                sp.Draw(g);
            }

            //绘制所有连线
            foreach (var ln in _links)
            {
                ln.Draw(g);
            }

            if (FCC_Mode == FCC_MODE.连线模式)
            {
                //获取鼠标在控件上的坐标
                var mousePt = this.PointToClient(MousePosition);

                //V5-PART2修改

                //判断是否有鼠标在形状上
                var sp = _shapes.Find(a => a.IsHitShape(mousePt));
                if (sp != null)
                {
                    //绘制所有连接点
                    sp.DrawAllLinkRects(g);

                    //再判断鼠标是否在连接点上
                    if (sp.IsHitLinkRect(mousePt))
                    {
                        //获取鼠标在哪个连接点上
                        var index=sp.GetHitLinkRect(mousePt);
                        //绘制此连接点的选中/聚集模式
                        sp.DrawFocusedLinkRect(g, index);
                    }
                }

                //绘制连接中的虚线箭头
                if (_isAddLink)
                {
                    //先绘制开始连接点
                    _selectedStartShape.DrawSelectedLinkRect(g, _linkPointStart);
                    //再绘制虚线
                    //注：课程里可实现一些不同的连接，讲一下扩展性。
                    var startPoint = _selectedStartShape.GetLinkPoint(_linkPointStart);
                    var endPoint = _lastMouseLocation;
                    g.DrawLine(_linkingLinePen, startPoint, endPoint);
                }
            }


            //绘制内存绘图到控件上
            e.Graphics.DrawImage(_bmp, new PointF(0, 0));

            //释放资源
            g.Dispose();

            base.OnPaint(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            //当鼠标按下时

            //V5-part2改变

            if (FCC_Mode==FCC_MODE.正常模式)
            {
                //取最上方的形状
                var sp = _shapes.FindLast(a => a.IsHitShape(e.Location));
                if (sp != null)
                {
                    //设置状态及选中矩形
                    _isMouseDown = true;
                    _lastMouseLocation = e.Location;
                    _selectedShape = sp;
                }
            }
            else if(FCC_Mode == FCC_MODE.连线模式)
            {
                //判断有没有点到连接点上
                var sp=_shapes.Find(a => a.IsHitLinkRect(e.Location));
                if (sp != null)
                {
                    _isAddLink = true;
                    //设置开始形状
                    _selectedStartShape = sp;
                    //设置开始形状的连接点
                    _linkPointStart = sp.GetHitLinkRect(e.Location);

                    _lastMouseLocation = e.Location;
                }

            }
            base.OnMouseDown(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            //当鼠标移动时

            //V5改变

            if (FCC_Mode == FCC_MODE.正常模式)
            {
                if (_isMouseDown)
                {
                    //当且仅当：有鼠标按下且有矩形被选中时，才进行后续操作

                    //改变选中矩形的位置信息，随着鼠标移动而移动

                    //计算鼠标位置变化信息
                    var moveX = e.Location.X - _lastMouseLocation.X;
                    var moveY = e.Location.Y - _lastMouseLocation.Y;

                    //将选中形状的位置进行同样的变化
                    var oldXY = _selectedShape.Rect.Location;
                    oldXY.Offset(moveX, moveY);
                    _selectedShape.Rect = new Rectangle(oldXY, _selectedShape.Rect.Size);

                    //记录当前鼠标位置
                    _lastMouseLocation.Offset(moveX, moveY);

                }
            }
            else if (FCC_Mode == FCC_MODE.连线模式)
            {
                if (_isAddLink)
                {
                    //处于添加连线状态
                    _lastMouseLocation = e.Location;
                }
            }

            //令当前控件失效以重绘
            Invalidate();

            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            //当鼠标松开时
            //V5修改

            if (FCC_Mode == FCC_MODE.正常模式)
            {
                if (_isMouseDown)
                {
                    //当且仅当：有鼠标按下且有矩形被选中时，才进行后续操作

                    //重置相关记录信息
                    _isMouseDown = false;
                    _lastMouseLocation = Point.Empty;
                    _selectedShape = null;
                }
            }
            else if (FCC_Mode == FCC_MODE.连线模式)
            {
                if (_isAddLink)
                {
                    //判断有没有点到连接点上
                    var sp = _shapes.Find(a => a.IsHitLinkRect(e.Location));
                    if (sp != null)
                    {
                        //判断鼠标所在连接点是否是同一形状
                        if (_selectedStartShape.Id != sp.Id)
                        {
                            //设置结束形状
                            _selectedEndShape = sp;
                            _linkPointEnd = sp.GetHitLinkRect(e.Location);

                            //两个形状都设置了，便添加一条新连线
                            switch (FCC_LinkType)
                            {
                                case "LineLink":
                                    _links.Add(new LineLinkV6()
                                    {
                                        Id = "直线连线" + Guid.NewGuid().ToString(),//这里就不能用数量了，防止重复
                                        BackgroundColor = FCC_LinkColor?.Invoke() ?? Color.Black,
                                        StartShape = _selectedStartShape,
                                        EndShape = _selectedEndShape,
                                        StartShapeLinkPoint = _linkPointStart,
                                        EndShapeLinkPoint = _linkPointEnd,
                                    });
                                    break;
                                case "BezierLink":
                                    _links.Add(new BezierLinkV6()
                                    {
                                        Id = "贝塞尔曲线连线" + Guid.NewGuid().ToString(),//这里就不能用数量了，防止重复
                                        BackgroundColor = FCC_LinkColor?.Invoke() ?? Color.Black,
                                        StartShape = _selectedStartShape,
                                        EndShape = _selectedEndShape,
                                        StartShapeLinkPoint = _linkPointStart,
                                        EndShapeLinkPoint = _linkPointEnd,
                                    });
                                    break;
                            }
                        }
                    }
                    
                    _isAddLink = false;
                    _lastMouseLocation = Point.Empty;

                    _selectedEndShape = null;
                    _selectedStartShape = null;

                }
            }

            //令当前控件失效以重绘
            Invalidate();

            base.OnMouseUp(e);
        }

        #endregion

    }
}
